package com.study.chunguard.common.utils.tree;

import cn.hutool.core.builder.Builder;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import org.springframework.beans.BeanUtils;

import java.util.*;

/**
 * 树构建器
 *
 * @param <T> ID类型
 * @author zhaowenhao
 * @since 2023-05-09
 */
public class TreeBuilder<T, N extends TreeNode<T, N>> implements Builder<N> {
    private static final long serialVersionUID = 1L;

    private final N root;

    /**
     * 是否生成父节点详情，父节点详情只包含父节点普通的字段，不包含父节点、子节点等信息，防止循环引用，
     * 由于生成父节点详情需要执行节点复制操作，非常影响性能，所以默认不生成
     */
    private Boolean shouldGenerateParentDetail = false;

    private final Map<T, N> idTreeMap;

    private boolean isBuild;

    /**
     * 树构造器，根据指定的根结点id和id-节点（不带子节点）Map构造一棵树，如果节点Map的key中不包含rootId，就使用TreeNode接口createEmpty()创建一个并设置id为rootId,用来作为根节点，然后逐级增加子节点。
     *
     * @param treeMap 节点Map（不带子节点）
     * @param rootId  根节点ID
     */
    public TreeBuilder(Map<T, N> treeMap, T rootId, boolean shouldGenerateParentDetail) {
        if (MapUtil.isEmpty(treeMap)) {
            this.root = null;
            this.idTreeMap = new LinkedHashMap<>();
            return;
        }
        N treeNode = treeMap.get(rootId);
        if (treeNode != null) {
            this.root = treeNode;
        } else {
            N other = treeMap.values().iterator().next().createEmpty();
            other.setId(rootId);
            this.root = other;
        }
        this.idTreeMap = new LinkedHashMap<>();
        this.shouldGenerateParentDetail = shouldGenerateParentDetail;
    }

    /**
     * 树构造器，根据指定的根结点id和节点列表（不带子节点）构造一棵树，如果节点列表中不包含id为rootId的元素，就使用TreeNode接口createEmpty()创建一个并设置id为rootId,用来作为根节点，然后逐级增加子节点。
     *
     * @param treeNodes                  节点列表（不带子节点）
     * @param rootId                     根节点ID
     * @param shouldGenerateParentDetail 是否生成父节点详情，父节点详情只包含父节点普通的字段，不包含父节点、子节点等信息，防止循环引用，
     *                                   由于生成父节点详情需要执行节点复制操作，非常影响性能，所以默认不生成
     */
    public TreeBuilder(List<N> treeNodes, T rootId, boolean shouldGenerateParentDetail) {
        if (CollUtil.isEmpty(treeNodes)) {
            this.root = null;
            this.idTreeMap = new LinkedHashMap<>();
            return;
        }
        Optional<N> first = treeNodes.stream()
                .filter(treeNode -> treeNode.getId().equals(rootId))
                .findFirst();
        if (first.isPresent()) {
            this.root = first.get();
        } else {
            N empty = treeNodes.get(0).createEmpty();
            empty.setId(rootId);
            this.root = empty;
        }
        this.idTreeMap = new LinkedHashMap<>();
        this.shouldGenerateParentDetail = shouldGenerateParentDetail;
    }

    /**
     * 增加节点列表，增加的节点是不带子节点的
     *
     * @param trees 节点列表
     * @return this
     */
    public TreeBuilder<T, N> append(Iterable<N> trees) {
        checkBuilt();

        for (N treeNode : trees) {
            // remove children if any
            treeNode.setChildren(null);
            this.idTreeMap.put(treeNode.getId(), treeNode);
        }
        return this;
    }

    /**
     * 增加节点列表，增加的节点是不带子节点的
     *
     * @param map 节点列表
     * @return this
     */
    public TreeBuilder<T, N> append(Map<T, N> map) {
        checkBuilt();

        for (N treeNode : map.values()) {
            // remove children if any
            treeNode.setChildren(null);
        }
        this.idTreeMap.putAll(map);
        return this;
    }

    /**
     * 重置Builder，实现复用
     *
     * @return this
     */
    public TreeBuilder<T, N> reset() {
        this.idTreeMap.clear();
        this.root.setChildren(null);
        this.isBuild = false;
        return this;
    }

    @Override
    public N build() {
        checkBuilt();

        buildFromMap();
        cutTree(-1);

        this.isBuild = true;
        this.idTreeMap.clear();

        return root;
    }

    /**
     * 构建树列表，没有顶层节点，例如：
     *
     * <pre>
     * -用户管理
     *  -用户管理
     *    +用户添加
     * - 部门管理
     *  -部门管理
     *    +部门添加
     * </pre>
     *
     * @return 树列表
     */
    public List<N> buildList() {
        if (isBuild) {
            // 已经构建过了
            return this.root.getChildren();
        }
        return build().getChildren();
    }

    /**
     * 开始构建
     */
    private void buildFromMap() {
        if (MapUtil.isEmpty(this.idTreeMap)) {
            return;
        }

        final Map<T, N> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false);
        T parentId;
        for (N node : eTreeMap.values()) {
            if (null == node) {
                continue;
            }
            parentId = node.getParentId();
            if (ObjectUtil.equals(this.root.getId(), parentId)) {
                this.root.addChildren(Collections.singletonList(node));
                if (Boolean.TRUE.equals(shouldGenerateParentDetail)) {
                    // 生成父节点详情
                    node.setParentDetail(cloneWithoutRelations(this.root));
                }
                continue;
            }

            final N parentNode = eTreeMap.get(parentId);
            if (null != parentNode) {
                if (Boolean.TRUE.equals(shouldGenerateParentDetail)) {
                    // 生成父节点详情
                    node.setParentDetail(cloneWithoutRelations(parentNode));
                }
                parentNode.addChildren(Collections.singletonList(node));
            }
        }
    }

    /**
     * 生成父节点详情,父节点详情只包含父节点普通的字段，不包含父节点、子节点等信息，防止循环引用
     *
     * @param node 节点
     * @return 新的节点对象，不包含 children 和 parent
     */
    private N cloneWithoutRelations(N node) {
        // 使用复制构造函数创建一个新的对象
        N clone = node.createEmpty();
        BeanUtils.copyProperties(node, clone);
        // 设置 children 为 null
        clone.setChildren(null);
        // 设置 parent 为 null
        clone.setParent(null);
        // 设置 parentDetail 为 null
        clone.setParentDetail(null);
        return clone;
    }

    /**
     * 树剪枝
     *
     * @param deep 最大层级
     */
    private void cutTree(Integer deep) {
        if (null == deep || deep < 0) {
            return;
        }
        cutTree(this.root, 0, deep);
    }

    /**
     * 树剪枝叶
     *
     * @param treeNode    节点
     * @param currentDepp 当前层级
     * @param maxDeep     最大层级
     */
    private void cutTree(N treeNode, int currentDepp, int maxDeep) {
        if (null == treeNode) {
            return;
        }
        if (currentDepp == maxDeep) {
            // 剪枝
            treeNode.setChildren(null);
            return;
        }

        final List<N> children = treeNode.getChildren();
        if (CollUtil.isNotEmpty(children)) {
            for (N child : children) {
                cutTree(child, currentDepp + 1, maxDeep);
            }
        }
    }

    /**
     * 检查是否已经构建
     */
    private void checkBuilt() {
        Assert.isFalse(isBuild, "Current tree has been built.");
    }
}
